#pragma once  

#include <list>
#include <mutex>
#include <condition_variable>

template <typename T>
class SyncQueue
{
public:
	SyncQueue(int maxSize): m_maxSize(maxSize), m_needStop(false) {}

	void Put(const T& t)
	{
		Add(t);
	}

	void Put(T&& t)
	{
		Add(std::forward<T>(t));
	}

	void Take(std::list<T>& list)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_notEmpty.wait(lock, [this] { return m_needStop || NotEmpty(); });
		if (m_needStop)
			return;
		list = std::move(m_queue);
		m_notFull.notify_one();
	}

	void Take(T& t)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_notEmpty.wait(lock, [this] { return m_needStop || NotEmpty(); });
		if (m_needStop)
			return;
		t = m_queue.front();
		m_queue.pop_front();
		m_notFull.notify_one();
	}

	void Stop()
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_needStop = true;
		m_notFull.notify_all();
		m_notEmpty.notify_all();
	}

	bool Empty()
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		return m_queue.empty();
	}

	bool Full()
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		return m_queue.size() == m_maxSize;
	}

	size_t Size()
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		return m_queue.size();
	}
private:
	bool NotFull() const
	{
		bool full = ((int)m_queue.size()) >= m_maxSize;
		return !full;
	}

	bool NotEmpty() const
	{
		bool empty = m_queue.empty();
		return !empty;
	}

	template<typename F>
	void Add(F&& f)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_notFull.wait(lock, [this] { return m_needStop || NotFull(); });
		if (m_needStop)
		{
			return;
		}
		m_queue.push_back(std::forward<F>(f));
		m_notEmpty.notify_one();
	}
private:
	std::list<T> m_queue;
	std::mutex m_mutex;
	std::condition_variable m_notEmpty;
	std::condition_variable m_notFull;
	int m_maxSize;
	bool m_needStop;
};